#include<iostream>
#include<vector>
#include<cassert>
using namespace std;

struct augment_path {
  vector<vector<int> > g;
  vector<int> pa;  // 匹配
  vector<int> pb;
  vector<int> vis;  // 访问
  int n, m;         // 两个点集中的顶点数量
  int dfn;          // 时间戳记
  int res;          // 匹配数

  augment_path(int _n, int _m) : n(_n), m(_m) {
    assert(0 <= n && 0 <= m);
    pa = vector<int>(n, -1);
    pb = vector<int>(m, -1);
    vis = vector<int>(n);
    g.resize(n);
    res = 0;
    dfn = 0;
  }

  void add(int from, int to) {
    assert(0 <= from && from < n && 0 <= to && to < m);
    g[from].push_back(to);
  }

  // 深度优先搜索增广路算法
  // 若找到增广路，则反转增广路上的边，并返回true，否则返回false
  bool dfs(int v) {
    // 记录节点v已经访问过
    // 为什么只需记录左边节点v是否访问？
    // 使得从左往右走时，只会选中非匹配边
    vis[v] = dfn;
    // 边界条件：右边节点u为非匹配点
    // 查看左边的节点v所连接的右边的节点u是否为匹配点
    for (int u : g[v]) {
      // label1
      // 如果存在一个u为非匹配点，则将v与u匹配，
      if (pb[u] == -1) {
        pb[u] = v;
        pa[v] = u;
        return true;
      }
    }
    // 运行到此处，则左边的节点v所连接的右边的节点u都是匹配点，故v为未匹配点，继续寻找增广路
    // 确保往左走必定走匹配边（pb[u]必定不是-1）
    // 确保往右走必定走非匹配边（v一定是匹配点，且只能匹配一条匹配边，且必定是从右边的匹配点u走到v的，由vis[pb[u]] != dfn确保不走回头路（即不选择刚走过的匹配边），因此从左往右必定是非匹配边）

    for (int u : g[v]) {
      //若dfs返回为true，则表示找到一条增广路，且已经完成除第一条边以外的边的反转（每次均由“非匹配边-->匹配边-->非匹配边”三边四点组成，第二条非匹配边由label1反转，第一条非匹配边由此处反转，中间的匹配边自然转为非匹配边）
      // 此时只需反转第一条边即可（非匹配边-->匹配边）
      if (vis[pb[u]] != dfn && dfs(pb[u])) {
        pa[v] = u;
        pb[u] = v;
        return true; //注意找到一条增广路即可返回，即使还有另外的增广路，也不应该继续寻找
      }
      //由于dfs的递归调用性质，长链路的增广路由最末端的最短增广路开始反转
    }
    return false;
  }

  int solve() {
    while (true) {
      dfn++;
      int cnt = 0;
      for (int i = 0; i < n; i++) {
        // 若dfs为true，表示找到一条增广路，反转增广路上的边，则匹配数量加1
        if (pa[i] == -1 && dfs(i)) {
          cnt++;
        }
      }
      if (cnt == 0) {
        break;
      }
      res += cnt;
    }
    return res;
  }
};


int main()
{

  struct augment_path ap(5, 5);
  // ap.add(0, 0);
  // ap.add(0, 1);
  // ap.add(1, 0);
  // ap.add(2, 1);
  // ap.add(2, 2);
  // ap.add(3, 2);
  // ap.add(3, 1);
  // ap.add(3, 3);
  // ap.add(4, 3);
  // ap.add(4, 4);

  ap.add(0, 0);
  ap.add(0, 1);
  ap.add(1, 0);
  ap.add(2, 0);
  ap.add(2, 1);
  ap.add(3, 0);
  ap.add(3, 2);
  ap.add(3, 3);


  std::cout << ap.solve() << std::endl;
  

    return 0;
}